#pragma once
#include "Globals.hpp"
#include "Timer.hpp"
#include "General Utils.hpp"
#include "Entity.h"
#include "PathPlanner.h"
#include "Vector2D.h"
#include "MapPathCreator.h"
#include <string>
#include <vector>
using namespace std;
//#ifdef CONSOLE_DEBUG
//#define MAKE_MAP_GENERATOR_SUPER_WORDY	// this define prints out generated maps and generated rooms
//#endif									// comment it out to remove these outputs
/*
*	This file contains functions for Lua that allow random map scripts to be created.
*
*	roomID:		a continent, a lake, an island, a group of trees, some iron...
*	mapID:		something like map1.map, map2.map etc
*	GFXID:		grass, but it contains more data (like how to surround grass to make it look good)
*
*	Usage:
*		Create a map with CreateMap(...) and save the ID it returns
*		Register this ID with RegisterMap(...)
*		AddRooms
*		AddPaths (N.Y.I.)
*		SaveMap(...)
*
*	Type:
*		Run of the mill singleton
*/

#define MapGen MapGenerator::Instance()

struct GFXID
{
	int base, topLeft, bottomLeft, bottomRight, topRight, left, bottom, right, top;
};



enum RMG_Algorithms
{
	NONE, SNAKE_FULL, SNAKE_FAST, CHISEL, SCATTER, BOX, FOCUS_POINT
};

struct Room
{
	int x1, y1, x2, y2, w, h, gfxID, algorithm, ID;
	bool HasBeenCreated, isPath, isRiver;
	string name; // the name of the room
	vector<vector<vector<int>>> tiles; // So we can contain irregular shapes
	// LAYER HEIGHT WIDTH!
	// HEIGHT WIDTH LAYER (NEW)

	Room()
	{
		name = "default";
		algorithm = NONE;
		isPath = false;
		isRiver = false;
		HasBeenCreated = false;
		x1 = 0;
		y1 = 0;
		x2 = 0;
		y2 = 0;
		w = 0;
		h = 0;
		gfxID = -1;
		ID = -1;
	}

	bool operator ==(const Room& rhs)
	{
		if(this->ID == rhs.ID)
			return true;
		return false;
	}

	// Ensures that x1 / y1 are smaller than x2 / y2 and recalculates w and h
	void EnsureDataIntegrity()
	{
		if(x1 > x2)
			Swap(x1, x2);
		if(y1 > y2)
			Swap(y1, y2);

		w = x2 - x1;
		h = y2 - y1;
	}

};

// --------------------------------------------------------------------------------------------------------------------
// The MapGenerator Class
class MapGenerator
{
private:


	struct Map
	{
		vector<Room> rooms;
		int mapWidth, mapHeight, baseID;
		SparseGraph<NavGraphNode<void*>, GraphEdge> *navGraph;
		MapPathCreator *pathCreator;

		bool NavGraphCreated;

		#ifdef CONSOLE_DEBUG 
		Timer timer;
		int totalRoomsGenerated;
		#endif

		void CreateNavGraph()
		{
			navGraph = SparseGraph<NavGraphNode<void*>, GraphEdge>::GetGraph(DataLoader->GetMapWidth(), DataLoader->GetMapHeight());
			NavGraphCreated = true;
			mapWidth = DataLoader->GetMapWidth();
			mapHeight = DataLoader->GetMapHeight();
			pathCreator = new MapPathCreator(*navGraph, mapWidth, mapHeight);
		}

		void DeleteNavGraph()
		{
			if(NavGraphCreated)
			{
				NullDelete(navGraph);
				NullDelete(pathCreator);
				NavGraphCreated = false;
			}
		}

		Map()
		{
			NavGraphCreated = false;
		}

		~Map()
		{
			DeleteNavGraph();
		}
	};



public: 

	static MapGenerator* Instance();

	static int	AddRoom	(Range x1, Range y1, Range x2, Range y2, double density, int imgID, string algorithm);
	static int	AddRoom	(Range x1, Range y1, Range x2, Range y2, double density, int imgID, int algorithm);	// Adds a room in the given location and returns a roomID
	static int	AddPath(int startRoomID, int endRoomID, int pathWidth, int imageID, double scatterDensity = -1, bool isRiver = false);	// Returns a roomID
	static int	MirrorRoom(int roomID, bool mirrorX = true, bool mirrorY = true);
	static int	CreateMap	(int gfxID, int width, int height);		// Returns the next available mapID and creates a map with all tiles as tileID
	// Registers what map we are adding roomIDs to
	static void	RegisterMap (int mapID)		{mActiveMap = mapID;}
	static void	SaveMap		(const char* path);	// Generates the map and saves it at the given path
	

	// This function registers a base tile, grass, with its border tiles, like grass_b_right
	// NOTE: All of the tiles should be set once, right when Lua is created before any maps are generated
	static void	AddGFXID	(string base, string topLeft, string bottomLeft, string bottomRight,
						string topRight, string left, string bottom, string right, string top);

	// Accessor
	static Room *GetRoom(int ID) {return &mMaps[mActiveMap].rooms[ID];}
	static Room GetLastRoom() {return mMaps[mActiveMap].rooms[mMaps[mActiveMap].rooms.size() - 1];}
	static void ChangeRoom(const Room& room);
	static void RemoveRoom(const Room& room);
	static  vector<GFXID>& GetGFXIDVector() {return mGFXID;}
	// Post processing
	static void PostProcessRoomCorrectWay(Room& room);

private:
	
	static Room CreateRoom(Range x1, Range y1, Range x2, Range y2, int imgID);
	static Room CreateRoom(Range x1, Range y1, Range x2, Range y2) {return CreateRoom(x1, y1, x2, y2, ImageManager->GetImageID("ignored_tile"));}
	static Room CreateRoom(int x1, int y1, int x2, int y2, int imgID = -1);
	static void	PostProcess(vector<vector<vector<int>>> &world);
	static void PostProcessRoom(Room &room);

	// Algorithms
	static void	SnakeFullAlgorithm(Room& room, double density, int imgID);
	static void	SnakeFastAlgorithm(Room& room, double density, int imgID);
	static void	ChiselAlgorithm(Room& room, int variation, int imgID);
	static void	SparseAlgorithm(Room& room, double density, int imgID);
	static void BoxAlgorithm(Room& room, double density, int imgID);
	static void FocusPointAlgorithm(Room& room, double baseKillPercentage, int imgID);
	

	int nextgfxID, nextRoomID, nextMapID;

	static vector<GFXID> mGFXID;
	static vector<Map> mMaps;

	static const int VARIATION_REMOVE = 5; // used in ChiselAlgorithm

	static int mActiveMap;
};